
/**
 * Baijiahulian.com Inc. Copyright (c) 2014-2016 All Rights Reserved.
 */

package cn.wenhao.javaClassReload.JavaClassModify.classTransformer;

import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.util.TraceClassVisitor;

import cn.wenhao.javaClassReload.JavaClassModify.ClassIncluder;
import cn.wenhao.javaClassReload.JavaClassModify.classTransformer.utils.ByteSourceClassLoader;
import cn.wenhao.javaClassReload.constant.Constant;
import cn.wenhao.javaClassReload.constant.ObjExecutorCache;
import cn.wenhao.javaClassReload.constant.ObjFieldMemberCache;
import cn.wenhao.javaClassReload.invoker.MethodInvoker;
import cn.wenhao.javaClassReload.testHelper.MethodPrinter;
import cn.wenhao.javaClassReload.utils.ClassPrinter;
import cn.wenhao.javaClassReload.utils.GenericsUtils;
import cn.wenhao.javaClassReload.utils.TupleUtil;
import cn.wenhao.javaClassReload.utils.TwoTuple;
import lombok.extern.slf4j.Slf4j;

/**
 * @say little Boy, don't be sad.
 * @name Rezar
 * @time Oct 28, 2016
 * @Desc 用于生成指定类某个方法的调用器
 */
@Slf4j
public class MethodInvokerCreator extends ClassVisitor implements Opcodes {

    // 进行字节码的输出
    private ClassWriter cw;

    private TraceClassVisitor checkouter; // TraceClassVisitor

    private String hostClass;
    private MethodNode methodNode;

    private String className;

    private int maxLocalSize;

    private String methodName;

    private boolean isConstructorMethod;

    private boolean needCreateInvoker;

    /**
     * @param api
     * @param cv
     */
    public MethodInvokerCreator(String hostClass, MethodNode methodNode, boolean needCreateInvoker) {
        super(ASM4, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
        this.cw = (ClassWriter) cv;
        this.hostClass = hostClass;
        this.methodNode = methodNode;
        this.maxLocalSize = this.methodNode.maxLocals;
        this.className = getInvokerClassName(this.hostClass, this.methodNode.name, this.methodNode.desc);
        this.methodName = this.methodNode.name;
        this.isConstructorMethod = isConstructorMethod();
        log.debug("create class : {} ", this.className);
        // CheckClassAdapter cca = new CheckClassAdapter(cw);

        if (Constant.IS_PRINT) {
            System.out.println("================= before change , the method code is : =========================");
            MethodPrinter mp = new MethodPrinter(System.out);
            this.methodNode.accept(mp);
            System.out.println("==========================================");
        }
        checkouter = new TraceClassVisitor(cw, new PrintWriter(System.out));
        // String filePath = Constant.OUT_PUT_DIRECTORY + this.className.replace("/", "_") + ".txt";
        // log.info("file path is : {} ", filePath);
        // File outfile = new File(filePath);
        // if (!outfile.exists()) {
        // try {
        // outfile.createNewFile();
        // } catch (Exception e) {
        // e.printStackTrace();
        // }
        // }
        this.needCreateInvoker = needCreateInvoker;
        if (this.needCreateInvoker) {
            // 当前动态类生成
            classCreate();
            // 创建invoke方法实现
            invokeMethodCreate();
        }
        // 当前方法体字节码修改
        changeMethoInstruments();
    }

    /**
     * @return
     */
    private boolean isConstructorMethod() {
        String methodName = this.methodName;
        return methodName.equals("<init>");
    }

    /**
     * @param api
     * @param cv
     */
    public MethodInvokerCreator(String hostClass, MethodNode methodNode) {
        this(hostClass, methodNode, true);
    }

    public MethodNode afterTransform() {
        return this.methodNode;
    }

    public MethodInvoker createMetodInvoker() {
        try {
            byte[] invokeClassByteSource = this.invokeClassByteSource();
            if (Constant.IS_PRINT) {
                ClassPrinter.print("输出字节码:" + this.className, invokeClassByteSource);
            }
            return (MethodInvoker) ByteSourceClassLoader
                .loadClass(this.getClass().getClassLoader(), this.className.replace("/", "."), invokeClassByteSource)
                .newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            return null;
        }
    }

    public byte[] invokeClassByteSource() {
        this.checkouter.visitEnd();
        return this.cw.toByteArray();
    }

    @SuppressWarnings("unchecked")
    public Class<? extends MethodInvoker> loadInvokeClass(ClassLoader classLoader) {
        Class<? extends MethodInvoker> invokerClass = (Class<? extends MethodInvoker>) ByteSourceClassLoader
            .loadClass(classLoader, className, this.invokeClassByteSource());
        return invokerClass;
    }

    /**
     * 这个比较麻烦,注意转型操作<br/>
     */
    private void invokeMethodCreate() {
        MethodVisitor mv = null;
        log.debug("retType : {} " + this.methodNode);
        Type returnType = Type.getReturnType(this.methodNode.desc);
        log.debug("returnType : {} ", returnType.getClassName());
        boolean primitive = isPrimitive(returnType.getDescriptor());
        boolean hasRet = !(returnType.getClassName() == Type.getInternalName(void.class));
        log.debug("has ret : " + hasRet);

        {
            mv = cv.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke",
                "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
            mv.visitCode();
            if (hasRet) {
                log.debug("has ret ");
                visitWithHasRet(mv, returnType.getClassName(), primitive, hasRet);
            } else {
                log.debug("has no ret");
                visitWithNotRet(mv, returnType.getClassName(), primitive, hasRet);
            }
        }

        {
            mv = cw.visitMethod(ACC_PUBLIC, "setHost", "(Ljava/lang/Object;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitTypeInsn(CHECKCAST, this.hostClass);
            mv.visitFieldInsn(PUTFIELD, this.className, "host", "L" + this.hostClass + ";");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", "L" + this.className + ";", null, l0, l2, 0);
            mv.visitLocalVariable("host", "Ljava/lang/Object;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }

    }

    /**
     * @param mv
     * @param string
     * @param primitive
     * @param hasRet
     */
    private void visitWithNotRet(MethodVisitor mv, String returnType, boolean primitive, boolean hasRet) {
        Type[] argumentTypesWithInneralName = Type.getArgumentTypes(this.methodNode.desc);
        boolean hasArguments = argumentTypesWithInneralName.length > 0;
        int oldBaseIndex = 2;
        int baseIndex = 2;
        log.debug("arguType are : " + argumentTypesWithInneralName.length);
        if (hasArguments) {
            int i = 0;
            for (Type arguType : argumentTypesWithInneralName) {
                log.debug("arguType: {} and i : {} ", arguType, i);
                Label label = new Label();
                Class<?> wrapperType = getWrapperType(arguType.getClassName());
                String innerName = null;
                if (wrapperType != null) {
                    innerName = Type.getInternalName(wrapperType);
                } else {
                    innerName = arguType.getInternalName();
                }
                mv.visitLabel(label);
                mv.visitVarInsn(ALOAD, 2);
                log.debug("i:" + i);
                if (i < 6) {
                    mv.visitInsn(getICONST(i));
                } else {
                    mv.visitIntInsn(BIPUSH, i);
                }
                mv.visitInsn(AALOAD);
                mv.visitTypeInsn(CHECKCAST, innerName);
                mv.visitVarInsn(ASTORE, ++baseIndex);
                i++;
            }
        }
        log.debug("baseIndex : " + baseIndex);

        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 0);
        int i = oldBaseIndex + 1;
        if (hasArguments) {
            for (; i <= baseIndex; i++) {
                mv.visitVarInsn(ALOAD, i);
                if (isPrimitive(argumentTypesWithInneralName[i - oldBaseIndex - 1].getDescriptor())) {
                    wrapperChange(mv, getWrapperType(
                        changeToInnerName(argumentTypesWithInneralName[i - oldBaseIndex - 1].getClassName())));
                }
            }
        }
        log.debug("==========");
        if (Modifier.isStatic(this.methodNode.access)) {
            mv.visitMethodInsn(INVOKESTATIC, this.className, this.methodName, this.methodNode.desc);
        } else {
            mv.visitMethodInsn(INVOKEVIRTUAL, this.className, this.methodName, this.methodNode.desc);
        }
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitInsn(ACONST_NULL);
        mv.visitInsn(ARETURN);
        Label l2 = new Label();
        mv.visitLabel(l2);
        if (hasArguments) {
            log.debug("maxStack: " + (1 + argumentTypesWithInneralName.length) + " , maxLocal"
                + (3 + argumentTypesWithInneralName.length));
            mv.visitMaxs(1 + argumentTypesWithInneralName.length, 3 + argumentTypesWithInneralName.length);
        } else {
            mv.visitMaxs(1, 3);
        }
        mv.visitEnd();

    }

    /**
     * @param primitive
     * @param hasRet
     */
    private void visitWithHasRet(MethodVisitor mv, String returnType, boolean primitive, boolean hasRet) {
        Type[] argumentTypesWithInneralName = Type.getArgumentTypes(this.methodNode.desc);
        boolean hasArguments = argumentTypesWithInneralName.length > 0;
        int oldBaseIndex = 2;
        int baseIndex = 2;
        log.debug("arguType are : " + argumentTypesWithInneralName.length);
        if (hasArguments) {
            int i = 0;
            for (Type arguType : argumentTypesWithInneralName) {
                log.debug("arguType: {} and i : {} ", arguType, i);
                Label label = new Label();
                Class<?> wrapperType = getWrapperType(arguType.getClassName());
                String innerName = null;
                if (wrapperType != null) {
                    innerName = Type.getInternalName(wrapperType);
                } else {
                    innerName = arguType.getInternalName();
                }
                mv.visitLabel(label);
                mv.visitVarInsn(ALOAD, 2);
                if (i < 6) {
                    mv.visitInsn(getICONST(i));
                } else {
                    mv.visitIntInsn(BIPUSH, i);
                }
                mv.visitInsn(AALOAD);
                mv.visitTypeInsn(CHECKCAST, innerName);
                mv.visitVarInsn(ASTORE, ++baseIndex);
                i++;
            }
        }
        log.debug("baseIndex is : " + baseIndex);
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 0);
        int i = oldBaseIndex + 1;
        if (hasArguments) {
            for (; i <= baseIndex; i++) {
                mv.visitVarInsn(ALOAD, i);
                if (isPrimitive(argumentTypesWithInneralName[i - oldBaseIndex - 1].getDescriptor())) {
                    String changeToInnerName =
                        changeToInnerName(argumentTypesWithInneralName[i - oldBaseIndex - 1].getClassName());
                    Class<?> wrapperType = getWrapperType(changeToInnerName);
                    wrapperChange(mv, wrapperType);
                }
            }
        }
        if (Modifier.isStatic(this.methodNode.access)) {
            mv.visitMethodInsn(INVOKESTATIC, this.className, this.methodName, this.methodNode.desc);
        } else {
            mv.visitMethodInsn(INVOKEVIRTUAL, this.className, this.methodName, this.methodNode.desc);
        }
        if (primitive) {
            primitiveChange(mv, returnType);
        }
        mv.visitVarInsn(ASTORE, i);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitVarInsn(ALOAD, i);
        mv.visitInsn(ARETURN);
        Label l2 = new Label();
        mv.visitLabel(l2);
        if (hasArguments) {
            mv.visitMaxs(1 + argumentTypesWithInneralName.length, 4 + argumentTypesWithInneralName.length);
        } else {
            mv.visitMaxs(1, 4);
        }
        mv.visitEnd();
    }

    public static void wrapperChange(MethodVisitor mv, Class<?> wrapperType) {
        Method primitiveMethod = getPrimitiveMethod(wrapperType);
        mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(wrapperType), primitiveMethod.getName(),
            Type.getMethodDescriptor(primitiveMethod));
    }

    /**
     * @param wrapperType
     * @return
     */
    private static Method getPrimitiveMethod(Class<?> wrapperType) {
        String methodName = "";
        Class<?>[] emptyParamterTypes = new Class<?>[] {};
        if (wrapperType == Byte.class) {
            methodName = "byteValue";
        } else if (wrapperType == Boolean.class) {
            methodName = "booleanValue";
        } else if (wrapperType == Character.class) {
            methodName = "charValue";
        } else if (wrapperType == Short.class) {
            methodName = "shortValue";
        } else if (wrapperType == Integer.class) {
            methodName = "intValue";
        } else if (wrapperType == Float.class) {
            methodName = "floatValue";
        } else if (wrapperType == Long.class) {
            methodName = "longValue";
        } else if (wrapperType == Double.class) {
            methodName = "doubleValue";
        }
        log.debug("wrapperType :{} and methodName is : {} ", wrapperType, methodName);
        try {
            return wrapperType.getMethod(methodName, emptyParamterTypes);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param string
     * @return
     */

    private boolean isPrimitive(String typeDesc) {
        Type primitiveType = Type.getType(typeDesc);
        Class<?> wrapperType = getWrapperType(primitiveType.getClassName());
        return wrapperType != null;
    }

    /**
     * @param arguType
     * @return
     */
    private static Class<?> getWrapperType(String primitiveType) {
        if (primitiveType == int.class.getName()) {
            return Integer.class;
        } else if (primitiveType == boolean.class.getName()) {
            return Boolean.class;
        } else if (primitiveType == byte.class.getName()) {
            return Byte.class;
        } else if (primitiveType == float.class.getName()) {
            return Float.class;
        } else if (primitiveType == short.class.getName()) {
            return Short.class;
        } else if (primitiveType == long.class.getName()) {
            return Long.class;
        } else if (primitiveType == double.class.getName()) {
            return Double.class;
        } else if (primitiveType == char.class.getName()) {
            return Character.class;
        }
        return null;
    }

    public static void primitiveChange(MethodVisitor mv, String primitiveType) {
        Method wrapperTypeMethod = getWrapperMethod(primitiveType);
        Class<?> wrapperClass = wrapperTypeMethod.getDeclaringClass();
        log.debug(Type.getInternalName(wrapperClass) + "----" + Type.getMethodDescriptor(wrapperTypeMethod));
        mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(wrapperClass), "valueOf",
            Type.getMethodDescriptor(wrapperTypeMethod));
    }

    /**
     * @param primitiveType
     * @return
     */
    private static Method getWrapperMethod(String primitiveType) {
        Class<?> wrapperType = getWrapperType(primitiveType);
        try {
            return wrapperType.getMethod("valueOf", getPrimitiveClass(primitiveType));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param primitiveType
     * @return
     */
    private static Class<?> getPrimitiveClass(String primitiveType) {
        if (primitiveType == int.class.getName()) {
            return Integer.TYPE;
        } else if (primitiveType == boolean.class.getName()) {
            return Boolean.TYPE;
        } else if (primitiveType == byte.class.getName()) {
            return Byte.TYPE;
        } else if (primitiveType == float.class.getName()) {
            return Float.TYPE;
        } else if (primitiveType == short.class.getName()) {
            return Short.TYPE;
        } else if (primitiveType == long.class.getName()) {
            return Long.TYPE;
        } else if (primitiveType == double.class.getName()) {
            return Double.TYPE;
        } else if (primitiveType == char.class.getName()) {
            return Character.TYPE;
        }
        return null;
    }

    public static int getLoadOpcode(Class<?> primitiveType) {
        if (primitiveType == int.class || primitiveType == boolean.class || primitiveType == short.class
            || primitiveType == char.class || primitiveType == byte.class) {
            return Opcodes.ILOAD;
        } else if (primitiveType == long.class) {
            return Opcodes.LLOAD;
        } else if (primitiveType == float.class) {
            return Opcodes.FLOAD;
        } else if (primitiveType == double.class) {
            return Opcodes.DLOAD;
        } else {
            return Opcodes.ALOAD;
        }
    }

    /**
     * @param ilAdd
     * @param loadValues
     */
    private void arrayOperator(InsnList ilAdd, List<VarInsnNode> loadValues) {
        int arraySize = loadValues.size();
        if (loadValues.size() <= 5) {
            ilAdd.add(new InsnNode(getICONST(arraySize)));
        } else {
            ilAdd.add(new IntInsnNode(BIPUSH, arraySize));
        }
        ilAdd.add(new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
        for (int i = 0; i < arraySize; i++) {
            addDataToArray(ilAdd, i, loadValues.get(i));
        }
    }

    /**
     * @param i
     * @param varInsnNode
     */
    private void addDataToArray(InsnList ilAdd, int i, VarInsnNode varInsnNode) {
        ilAdd.add(new InsnNode(DUP));
        if (i <= 5) {
            ilAdd.add(new InsnNode(getICONST(i)));
        } else {
            ilAdd.add(new IntInsnNode(BIPUSH, i));
        }
        ilAdd.add(varInsnNode);
        ilAdd.add(new InsnNode(AASTORE));
    }

    /**
     * @param i
     * @return
     */
    private static int getICONST(int i) {
        switch (i) {
            case 0:
                return ICONST_0;
            case 1:
                return ICONST_1;
            case 2:
                return ICONST_2;
            case 3:
                return ICONST_3;
            case 4:
                return ICONST_4;
            case 5:
                return ICONST_5;
            default:
                break;
        }
        return 0;
    }

    /**
     * 动态生成一个class
     */
    private void classCreate() {

        MethodVisitor mv = null;
        FieldVisitor fv = null;
        this.checkouter.visit(V1_7, ACC_PUBLIC + ACC_SUPER, className, null, Type.getInternalName(Object.class),
            new String[] { Type.getInternalName(MethodInvoker.class) });

        {
            fv = checkouter.visitField(ACC_PUBLIC, "host", "L" + this.hostClass + ";", null, null);
            fv.visitEnd();
        }

        {
            mv = this.checkouter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "L" + this.className + ";", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
    }

    /**
     * @Desc 修改method的字节码指令
     * 
     *       TODO : INVOKESPECIAL 用于执行父类或者私有方法.注意需要同样进行处理
     */
    @SuppressWarnings("unchecked")
    private void changeMethoInstruments() {
        InsnList insns = this.methodNode.instructions;
        TwoTuple<AbstractInsnNode, AbstractInsnNode> forConstructor = null;
        AbstractInsnNode aloadForInitAload0 = null;
        AbstractInsnNode aloadForInitInsert = null;
        if (this.isConstructorMethod) {
            forConstructor = addDefaultFieldInitCode(insns);
            if (forConstructor != null) {
                aloadForInitAload0 = forConstructor.first;
                aloadForInitInsert = forConstructor.second;
            }
        }
        Iterator<AbstractInsnNode> instrs = insns.iterator();
        while (instrs.hasNext()) {
            AbstractInsnNode il = instrs.next();
            if (il == aloadForInitAload0) {
                log.debug("find aloadForInitAload0 : {} and will continue", aloadForInitAload0);
                continue;
            }
            if (il.getOpcode() == INVOKEVIRTUAL) { // || il.getOpcode() == INVOKESPECIAL //TODO 需要处理实例化一个对象时候调用的构造函数
                ChangeInvokeVirtual(insns, il);
            } else if (il.getOpcode() == INVOKESTATIC) {// 调用某个类的静态方法
                changeInvokeStatic(insns, il);
            } else if (il.getOpcode() == PUTFIELD) {// 设置某个对象的属性值
                changePutField(insns, il);
            } else if (il.getOpcode() == GETFIELD) {// 获取某个对象的属性值
                changeGetField(insns, il);
            } else if (il.getOpcode() == PUTSTATIC) { // 设置某个对象的静态成员
                changePutStatic(insns, il);
            } else if (il.getOpcode() == GETSTATIC) { // 获取某个对象的静态成员
                changeGetStatic(insns, il);
            } else if (il.getOpcode() == ALOAD) { // 处理this的问题,但不处理构造函数里面的
                VarInsnNode vin = (VarInsnNode) il;
                int var = vin.var;
                if (var == 0 && !this.isConstructorMethod) {
                    changeAloadThis(insns, il);
                }
            }
        }

        if (aloadForInitInsert != null) {
            InsnList ilAdd = addDefaultFieldInitInsns();
            insns.insert(aloadForInitInsert, ilAdd);
        }

        this.methodNode.maxLocals = this.maxLocalSize;
        this.methodNode.maxStack = this.methodNode.maxStack + 4; // 构建可变参数数组的时候涉及到创建,DUP,转型的等操作,该值存在膨胀
        log.debug("max localsize : {} and maxStack : {} ", this.maxLocalSize, this.methodNode.maxStack);
        List<String> exceptions = this.methodNode.exceptions;
        String[] excepts = null;
        if (GenericsUtils.notNullAndEmpty(exceptions)) {
            excepts = exceptions.toArray(new String[exceptions.size()]);
        }
        log.debug("methodNode is : {} methodName is :{} ", this.methodNode, methodName);
        if (this.needCreateInvoker) {
            MethodVisitor visitMethod = this.checkouter.visitMethod(this.methodNode.access, this.methodName,
                this.methodNode.desc, this.methodNode.signature, excepts);
            this.methodNode.accept(visitMethod);
        }
    }

    /**
     * @param insns
     * @param il
     */
    private void changeGetStatic(InsnList insns, AbstractInsnNode il) {
        FieldInsnNode fin = (FieldInsnNode) il;
        String owner = fin.owner;
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("class:{} need'n change bytecode ", owner);
            return;
        }
        InsnList ilAdd = new InsnList();
        ilAdd.add(new LdcInsnNode(owner));
        ilAdd.add(new LdcInsnNode(fin.name));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjFieldMemberCache.class),
            Constant.DEFAULT_INVOKE_FIELD_GET_NAME, Constant.DEFAULT_INVOKE_FIELD_GET_DESC));
        String fieldType = Type.getType(fin.desc).getClassName();
        String returnTypeWrapper = getReturnType(fieldType);
        ilAdd.add(new TypeInsnNode(CHECKCAST, returnTypeWrapper.replace(".", "/")));
        if (isPrimitive(fin.desc)) {
            MethodInsnNode changeToPrimitive = primitiveChange(fieldType);
            ilAdd.add(changeToPrimitive);
        }
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * @param insns
     * @param il
     */
    private void changePutStatic(InsnList insns, AbstractInsnNode il) {
        FieldInsnNode fin = (FieldInsnNode) il;
        String fieldDesc = fin.desc;
        String owner = fin.owner;
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("class:{} need'n change bytecode ", owner);
            return;
        }
        Type typeOfField = Type.getType(fieldDesc);
        Type[] types = new Type[] { typeOfField };

        InsnList ilAdd = new InsnList();
        int localIndex = this.methodNode.maxLocals + 1;
        List<VarInsnNode> storeNodes = new ArrayList<>();
        for (int i = 1; i <= 1; i++) {
            TwoTuple<MethodInsnNode, VarInsnNode> storeNode = getStoreNode(types[i - 1], localIndex, ilAdd);
            if (storeNode.first != null) {
                ilAdd.add(storeNode.first);
            }
            ilAdd.add(storeNode.second);
            storeNodes.add(getLoadNode(storeNode.second));
            if (storeNode.second.getOpcode() == DSTORE || storeNode.second.getOpcode() == LSTORE) {
                localIndex += 2;
            } else {
                localIndex += 1;
            }
        }
        // 准备替换本地变量表的最大值
        if (this.maxLocalSize < localIndex) {
            this.maxLocalSize = localIndex;
        }
        Collections.reverse(storeNodes);
        ilAdd.add(new LdcInsnNode(owner));
        for (VarInsnNode vin : storeNodes) {
            ilAdd.add(vin);
        }
        ilAdd.add(new LdcInsnNode(fin.name));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjFieldMemberCache.class),
            Constant.DEFAULT_INVOKE_FIELD_PUT_NAME, Constant.DEFAULT_INVOKE_FIELD_PUT_DESC));
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * 添加初始化代码块,对属性进行添加,添加操作必须是在调用了 mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object",
     * "<init>", "()V", false);
     * 
     * 
     * @param insns
     * @return
     */
    @SuppressWarnings("unchecked")
    private TwoTuple<AbstractInsnNode, AbstractInsnNode> addDefaultFieldInitCode(InsnList insns) {
        ListIterator<AbstractInsnNode> iterator = insns.iterator();
        while (iterator.hasNext()) {
            AbstractInsnNode next = iterator.next();
            if (next.getOpcode() == ALOAD) {
                AbstractInsnNode next2 = next.getNext();
                if (next2 != null && next2.getOpcode() == INVOKESPECIAL) {
                    return TupleUtil.tuple(next, next2);
                }
            }
        }
        return null;
    }

    /**
     * @return
     */
    private InsnList addDefaultFieldInitInsns() {

        InsnList ilAdd = new InsnList();

        LabelNode ln = new LabelNode();
        ilAdd.add(ln);
        ilAdd.add(new VarInsnNode(ALOAD, 0));
        ilAdd.add(new VarInsnNode(ALOAD, 0));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjExecutorCache.class),
            Constant.DEFAULT_GET_OBJ_EXECUTOR_METHOD_NAME, Constant.DEFAULT_GET_OBJ_EXECUTOR_METHOD_DESC));
        ilAdd.add(new FieldInsnNode(PUTFIELD, this.hostClass, Constant.DEFAULT_FIELD_FOR_OBJ_EXECUTOR,
            Constant.DEFAULT_FIELD_FOR_OBJ_EXECUTOR_DESC));

        LabelNode ln2 = new LabelNode();
        ilAdd.add(ln2);
        ilAdd.add(new VarInsnNode(ALOAD, 0));
        ilAdd.add(new VarInsnNode(ALOAD, 0));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjFieldMemberCache.class),
            Constant.DEFAULT_GET_FIELD_MEMBER_EXECUTOR_METHOD_NAME,
            Constant.DEFAULT_GET_FIELD_MEMBER_EXECUTOR_METHOD_DESC));
        ilAdd.add(new FieldInsnNode(PUTFIELD, this.hostClass, Constant.DEFAULT_FIELD_FOR_OBJ_FIELD_MEMBER,
            Constant.DEFAULT_FIELD_FOR_OBJ_FIELD_MEMBER_DESC));

        this.methodNode.maxStack = this.methodNode.maxStack + 2;

        return ilAdd;
    }

    /**
     * @param insns
     * @param il
     */
    private void changeAloadThis(InsnList insns, AbstractInsnNode il) {
        InsnList ilAdd = new InsnList();
        ilAdd.add(new VarInsnNode(ALOAD, 0));
        ilAdd.add(new FieldInsnNode(GETFIELD, this.className, "host", "L" + this.hostClass + ";"));
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * @param insns
     * @param il
     */
    private void changeInvokeStatic(InsnList insns, AbstractInsnNode il) {
        MethodInsnNode min = (MethodInsnNode) il;
        String targetMethodName = min.name;
        String targetMethodDesc = min.desc;
        String strConstant = targetMethodName + targetMethodDesc;
        String owner = min.owner;
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("===== class:{} need'n change bytecode ", owner);
            return;
        }
        Type returnType = Type.getReturnType(targetMethodDesc);
        log.debug("returnType : {} ", returnType.getClassName());
        boolean hasRet = !(returnType.getClassName() == Type.getInternalName(void.class));
        log.debug("has ret : " + hasRet);
        boolean primitive = isPrimitive(returnType.getClassName());
        Type[] argumentTypes = Type.getArgumentTypes(targetMethodDesc);

        List<Type> needStoreTypes = new ArrayList<>();
        needStoreTypes.addAll(Arrays.asList(argumentTypes));
        Collections.reverse(needStoreTypes);

        InsnList ilAdd = new InsnList();
        int localIndex = this.methodNode.maxLocals + 1;
        List<VarInsnNode> storeNodes = new ArrayList<>();
        for (int i = 1; i <= needStoreTypes.size(); i++) {
            TwoTuple<MethodInsnNode, VarInsnNode> storeNode =
                getStoreNode(needStoreTypes.get(i - 1), localIndex, ilAdd);
            if (storeNode.first != null) {
                ilAdd.add(storeNode.first);
            }
            ilAdd.add(storeNode.second);
            storeNodes.add(getLoadNode(storeNode.second));
            if (storeNode.second.getOpcode() == DSTORE || storeNode.second.getOpcode() == LSTORE) {
                localIndex += 2;
            } else {
                localIndex += 1;
            }
        }
        // 准备替换本地变量表的最大值
        if (this.maxLocalSize < localIndex) {
            this.maxLocalSize = localIndex;
        }
        Collections.reverse(storeNodes);
        ilAdd.add(new LdcInsnNode(this.hostClass));
        ilAdd.add(new LdcInsnNode(strConstant));
        arrayOperator(ilAdd, storeNodes);
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjExecutorCache.class),
            Constant.DEFAULT_INVOKE_METHOD_NAME, Constant.DEFAULT_INVOKE_METHOD_DESC));
        boolean nextIsPop = false;
        if (il.getNext() != null && (il.getNext().getOpcode() == POP || il.getNext().getOpcode() == POP2)) {
            nextIsPop = true;
        }
        if (!nextIsPop) {
            if (hasRet) {
                String returnTypeWrapper = getReturnType(returnType.getClassName());
                ilAdd.add(new TypeInsnNode(CHECKCAST, returnTypeWrapper.replace(".", "/")));
                if (primitive) {
                    MethodInsnNode changeToPrimitive = primitiveChange(returnType.getClassName());
                    ilAdd.add(changeToPrimitive);
                }
            } else {
                ilAdd.add(new InsnNode(POP));
            }
        }
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * 
     * TODO : <br/>
     * 
     * @problem1:有碰到 this 的问题
     * @problem2:如何设置初始值
     * @param insns
     * @param il
     */
    private void changeGetField(InsnList insns, AbstractInsnNode il) {
        FieldInsnNode fin = (FieldInsnNode) il;
        String owner = fin.owner;
        String fname = fin.name;
        if (fname == Constant.DEFAULT_FIELD_FOR_OBJ_FIELD_MEMBER) {
            return;
        }
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("class:{} need'n change bytecode ", owner);
            return;
        }
        InsnList ilAdd = new InsnList();
        ilAdd.add(new LdcInsnNode(fin.name));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjFieldMemberCache.class),
            Constant.DEFAULT_INVOKE_FIELD_GET_NAME, Constant.DEFAULT_INVOKE_FIELD_GET_DESC));
        String fieldType = Type.getType(fin.desc).getClassName();
        String returnTypeWrapper = getReturnType(fieldType);
        ilAdd.add(new TypeInsnNode(CHECKCAST, returnTypeWrapper.replace(".", "/")));
        if (isPrimitive(fin.desc)) {
            MethodInsnNode changeToPrimitive = primitiveChange(fieldType);
            ilAdd.add(changeToPrimitive);
        }
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * TODO : <br/>
     * 
     * @problem1:有碰到 this 的问题
     * @problem2:如何设置初始值
     * @param insns
     * @param il
     */
    private void changePutField(InsnList insns, AbstractInsnNode il) {
        FieldInsnNode fin = (FieldInsnNode) il;
        String fieldDesc = fin.desc;
        String owner = fin.owner;
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("class:{} need'n change bytecode ", owner);
            return;
        }
        Type typeOfField = Type.getType(fieldDesc);
        Type typeOfOwn = Type.getType("L" + owner + ";");
        Type[] types = new Type[] { typeOfField, typeOfOwn };

        InsnList ilAdd = new InsnList();
        int localIndex = this.methodNode.maxLocals + 1;
        List<VarInsnNode> storeNodes = new ArrayList<>();
        for (int i = 1; i <= 2; i++) {
            TwoTuple<MethodInsnNode, VarInsnNode> storeNode = getStoreNode(types[i - 1], localIndex, ilAdd);
            if (storeNode.first != null) {
                ilAdd.add(storeNode.first);
            }
            ilAdd.add(storeNode.second);
            storeNodes.add(getLoadNode(storeNode.second));
            if (storeNode.second.getOpcode() == DSTORE || storeNode.second.getOpcode() == LSTORE) {
                localIndex += 2;
            } else {
                localIndex += 1;
            }
        }
        // 准备替换本地变量表的最大值
        if (this.maxLocalSize < localIndex) {
            this.maxLocalSize = localIndex;
        }
        Collections.reverse(storeNodes);
        for (VarInsnNode vin : storeNodes) {
            ilAdd.add(vin);
        }
        ilAdd.add(new LdcInsnNode(fin.name));
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjFieldMemberCache.class),
            Constant.DEFAULT_INVOKE_FIELD_PUT_NAME, Constant.DEFAULT_INVOKE_FIELD_PUT_DESC));
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * @param insns
     * @param il
     */

    private void ChangeInvokeVirtual(InsnList insns, AbstractInsnNode il) {
        MethodInsnNode min = (MethodInsnNode) il;
        String desc = min.desc;
        String owner = min.owner;
        if (!ClassIncluder.needInclude(owner)) {
            log.debug("class:{} need'n change bytecode ", owner);
            return;
        }
        Type returnType = Type.getReturnType(desc);
        log.debug("returnType : {} ", returnType.getClassName());
        boolean hasRet = !(returnType.getClassName() == Type.getInternalName(void.class));
        log.debug("has ret : " + hasRet);
        boolean primitive = isPrimitive(returnType.getDescriptor());
        log.debug("isPrimitive : " + primitive);
        Type[] argumentTypes = Type.getArgumentTypes(desc);

        List<Type> needStoreTypes = new ArrayList<>();
        needStoreTypes.addAll(Arrays.asList(argumentTypes));
        Collections.reverse(needStoreTypes);
        needStoreTypes.add(Type.getType("L" + min.owner + ";"));
        log.debug("argumentTypes are : {} ", Arrays.asList(argumentTypes));
        String targetMethodName = min.name;
        String targetMethodDesc = min.desc;
        String strConstant = targetMethodName + targetMethodDesc;
        InsnList ilAdd = new InsnList();
        int localIndex = this.methodNode.maxLocals + 1;
        List<VarInsnNode> storeNodes = new ArrayList<>();
        for (int i = 1; i <= needStoreTypes.size(); i++) {
            TwoTuple<MethodInsnNode, VarInsnNode> storeNode =
                getStoreNode(needStoreTypes.get(i - 1), localIndex, ilAdd);
            if (storeNode.first != null) {
                ilAdd.add(storeNode.first);
            }
            ilAdd.add(storeNode.second);
            storeNodes.add(getLoadNode(storeNode.second));
            if (storeNode.second.getOpcode() == DSTORE || storeNode.second.getOpcode() == LSTORE) {
                localIndex += 2;
            } else {
                localIndex += 1;
            }
        }
        // 准备替换本地变量表的最大值
        if (this.maxLocalSize < localIndex) {
            this.maxLocalSize = localIndex;
        }
        Collections.reverse(storeNodes);
        AbstractInsnNode first = storeNodes.remove(0);
        ilAdd.add(first);
        ilAdd.add(new LdcInsnNode(strConstant));
        arrayOperator(ilAdd, storeNodes);
        ilAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(ObjExecutorCache.class),
            Constant.DEFAULT_INVOKE_METHOD_NAME, Constant.DEFAULT_INVOKE_METHOD_DESC));
        boolean nextIsPop = false;
        if (il.getNext() != null && (il.getNext().getOpcode() == POP || il.getNext().getOpcode() == POP2)) {
            nextIsPop = true;
        }
        if (!nextIsPop) {
            if (hasRet) {
                String returnTypeWrapper = getReturnType(returnType.getClassName());
                ilAdd.add(new TypeInsnNode(CHECKCAST, returnTypeWrapper.replace(".", "/")));
                if (primitive) {
                    MethodInsnNode changeToPrimitive = primitiveChange(returnType.getClassName());
                    ilAdd.add(changeToPrimitive);
                }
            } else {
                ilAdd.add(new InsnNode(POP));
            }
        }
        insns.insert(il.getPrevious(), ilAdd);
        insns.remove(il);
    }

    /**
     * @param remove
     * @return
     */

    private VarInsnNode getLoadNode(AbstractInsnNode remove) {
        VarInsnNode vin = (VarInsnNode) remove;
        int opcode = vin.getOpcode();
        int var = vin.var;
        if (opcode == ASTORE) {
            return new VarInsnNode(ALOAD, var);
        } else if (opcode == DSTORE) {
            return new VarInsnNode(DLOAD, var);
        } else if (opcode == LSTORE) {
            return new VarInsnNode(LLOAD, var);
        } else if (opcode == FSTORE) {
            return new VarInsnNode(FSTORE, var);
        } else if (opcode == ISTORE) {
            return new VarInsnNode(ILOAD, var);
        }
        return null;
    }

    /**
     * @param type
     * @param ilAdd
     * @param localVarIndex
     * @return
     */
    private TwoTuple<MethodInsnNode, VarInsnNode> getStoreNode(Type type, int localVarIndex, InsnList ilAdd) {
        VarInsnNode retNode = null;
        MethodInsnNode changePrimityToWrapper = null;
        String innerName = type.getClassName();
        String primitiveType = null;

        primitiveType = changeToInnerName(innerName);
        if (primitiveType != null) {
            Method wrapperMethod = getWrapperMethod(primitiveType);
            changePrimityToWrapper =
                new MethodInsnNode(INVOKESTATIC, Type.getInternalName(wrapperMethod.getDeclaringClass()), "valueOf",
                    Type.getMethodDescriptor(wrapperMethod));
        }
        retNode = new VarInsnNode(ASTORE, localVarIndex);
        return TupleUtil.tuple(changePrimityToWrapper, retNode);
    }

    /**
     * @param primitiveName
     * @param primitiveType
     * @return
     */
    private String changeToInnerName(String primitiveName) {
        String primitiveType = null;
        if (primitiveName.equals("int")) {
            primitiveType = int.class.getName();
        } else if (primitiveName.equals("byte")) {
            primitiveType = byte.class.getName();
        } else if (primitiveName.equals("char")) {
            primitiveType = char.class.getName();
        } else if (primitiveName.equals("short")) {
            primitiveType = short.class.getName();
        } else if (primitiveName.equals("boolean")) {
            primitiveType = boolean.class.getName();
        } else if (primitiveName.equals("long")) {
            primitiveType = long.class.getName();
        } else if (primitiveName.equals("float")) {
            primitiveType = float.class.getName();
        } else if (primitiveName.equals("double")) {
            primitiveType = double.class.getName();
        }
        return primitiveType;
    }

    /**
     * @param returnTypeWrapper
     * @return
     */

    private MethodInsnNode primitiveChange(String returnTypeWrapper) {
        Class<?> wrapperType = getWrapperType(returnTypeWrapper);
        Method primitiveMethod = getPrimitiveMethod(wrapperType);
        MethodInsnNode retNode =
            new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(primitiveMethod.getDeclaringClass()),
                primitiveMethod.getName(), Type.getMethodDescriptor(primitiveMethod));
        return retNode;
    }

    /**
     * @param className2
     * @return
     */
    private String getReturnType(String typeClassName) {
        Class<?> wrapperType = getWrapperType(typeClassName);
        if (wrapperType == null) {
            return typeClassName;
        }
        return wrapperType.getName();
    }

    /**
     * @param hostClass
     * @param method
     * @return
     */
    private static String getInvokerClassName(String hostClass, String methodName, String methodDesc) {
        methodName = methodName.replaceAll("<init>", "_init_");
        methodName = methodName.replaceAll("<clinit>", "_cinit_");
        return hostClass + "$" + methodName + "$" + Math.abs(methodDesc.hashCode());
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
        Type type2 = Type.getType(int.class);
        System.out.println(type2.getClassName());
        Class<Integer> type = Integer.TYPE;
        Method method2 = Integer.class.getMethod("valueOf", type);
        System.out.println("<<<<<<<<" + method2);
        Method[] methods = Integer.class.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> paramClass : parameterTypes) {
                System.out.println(">>>>>>>" + paramClass);
            }
        }

    }

}
